// An adaption of the "UncannyEyes" sketch (see eye_functions tab)
// for the TFT_eSPI library. As written the sketch is for driving
// two TFT displays.

// The size of the displayed eye is determined by the screen size and
// resolution. The eye image is 128 pixels wide. In humans the palpebral
// fissure (open eye) width is about 30mm so a low resolution, large
// pixel size display works best to show a scale eye image. Note that
// display manufacturers usually quote the diagonal measurement, so a
// 128 x 128 1.7" display or 128 x 160 2" display is about right.

// The number of displays and chip selects used are defined in the
// config.h tab. The display count can be set to 1. If using one
// TFT and the chip select for that display is already defined in
// the TFT_eSPI library then change the chip select pins to -1 in the
// "config.h" tab.

// The wiring for 2 TFT displays to an ESP32 is described in the
// "wiring" tab of this sketch.

// Configuration settings for the eye, eye style, display count,
// chip selects and x offsets can be defined in the sketch "config.h" tab.

// Performance (frames per second = fps) can be improved by using
// DMA (for SPI displays only) on ESP32 and STM32 processors. Use
// as high a SPI clock rate as is supported by the display. 27MHz
// minimum, some displays can be operated at higher clock rates in
// the range 40-80MHz.

// Single defaultEye performance for different processors
//                                  No DMA   With DMA
// ESP8266 (160MHz CPU) 40MHz SPI   36 fps
// ESP32 27MHz SPI                  53 fps     85 fps
// ESP32 40MHz SPI                  67 fps    102 fps
// ESP32 80MHz SPI                  82 fps    116 fps // Note: Few displays work reliably at 80MHz
// STM32F401 55MHz SPI              44 fps     90 fps
// STM32F446 55MHz SPI              83 fps    155 fps
// STM32F767 55MHz SPI             136 fps    197 fps

// DMA can be used with RP2040, STM32 and ESP32 processors when the interface
// is SPI, uncomment the next line:
// #define USE_DMA

// Load TFT driver library
#include <SPI.h>
#include <TFT_eSPI.h>
#include <AnimatedGIF.h>
#include <string>
#include "config.h"  // ****** CONFIGURATION IS DONE IN HERE ******
// A pixel buffer is used during eye rendering
#define BUFFER_SIZE 1024   // 128 to 1024 seems optimum
#define BUFFERS 1          // 1 buffer for no DMA
#define RGB_BRIGHTNESS 10  // Change white brightness (max 255)
#define RGB_BUILTIN 10
// A simple state machine is used to control eye blinks/winks:
#define NOBLINK 0  // Not currently engaged in a blink
#define ENBLINK 1  // Eyelid is currently closing
#define DEBLINK 2  // Eyelid is currently opening
const int16_t EYE_MID = 25;
const int16_t EYE_MIN = 0;
const int16_t EYE_MAX = 50;

typedef struct
{
  uint8_t state;       // NOBLINK/ENBLINK/DEBLINK
  uint32_t duration;   // Duration of blink state (micros)
  uint32_t startTime;  // Time (micros) of last state change
} eyeBlink;

struct
{                     // One-per-eye structure
  int16_t tft_cs;     // Chip select pin for each display
  eyeBlink blink;     // Current blink/wink state
  int16_t xposition;  // x position of eye image
} eye[NUM_EYES];

// This struct is populated in config.h
typedef struct
{                     // Struct is defined before including config.h --
  int8_t select;      // pin numbers for each eye's screen select line
  int8_t wink;        // and wink button (or -1 if none) specified there,
  uint8_t rotation;   // also display rotation and the x offset
  int16_t xposition;  // position of eye on the screen
} eyeInfo_t;


TFT_eSPI tft;  // A single instance is used for 1 or 2 displays

uint16_t pbuffer[BUFFERS][BUFFER_SIZE];  // Pixel rendering buffer
                                         // DMA buffer selection

eyeInfo_t eyeInfo[] = {
  { TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION },  // LEFT EYE chip select and wink pins, rotation and offset
  { TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION },  // RIGHT EYE chip select and wink pins, rotation and offset
};

bool dmaBuf = 0;

int16_t eyeX = EYE_MID;
int16_t eyeY = EYE_MID;


void tryReadNewEyeXY() {
  uint8_t buffer[8] = { '\0' };
  size_t size = 7;
  int rx = Serial.read(buffer, size);
  if (rx == 0 || rx == -1 || rx != 7) {
    //Serial.printf("Serial data err, read count: %u\n", rx);
    return;
  }
  neopixelWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0);  // Green
  if (buffer[0] != ':' || buffer[3] != ',' || buffer[6] != '.') {
    Serial.printf("Serial rx format error: %s\n", buffer);
    return;
  }
  std::string cmd((char *)buffer);
  int x = std::stoi(cmd.substr(1, 2));
  int y = std::stoi(cmd.substr(4, 2));
  if (x > EYE_MAX || y > EYE_MAX) {
    Serial.printf("Serial recieve invaild data: %u/%u\n", x, y);
    return;
  }
  Serial.printf("Serial recieve: %u/%u\n", x, y);
  eyeX = x;
  eyeY = y;
  neopixelWrite(RGB_BUILTIN, RGB_BRIGHTNESS, 0, 0);  // Red
}

// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
  neopixelWrite(RGB_BUILTIN, 0, 0, RGB_BRIGHTNESS);  // Blue

  Serial.begin(115200);
  // while (!Serial);
  Serial.println("Starting");

  // Initialise the eye(s), this will set all chip selects low for the tft.init()
  initEyes();

  // Initialise TFT
  tft.init();

  // Raise chip select(s) so that displays can be individually configured
  digitalWrite(eye[0].tft_cs, HIGH);
  if (NUM_EYES > 1)
    digitalWrite(eye[1].tft_cs, HIGH);

  for (uint8_t e = 0; e < NUM_EYES; e++) {
    digitalWrite(eye[e].tft_cs, LOW);
    tft.setRotation(eyeInfo[e].rotation);
    tft.fillScreen(TFT_BLACK);
    digitalWrite(eye[e].tft_cs, HIGH);
  }
}

// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
  updateEye(eyeX, eyeY);
  tryReadNewEyeXY();
}
